package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.client.ProductFeignClient;
import com.atguigu.gmall.list.repository.GoodsRepository;
import com.atguigu.gmall.list.service.SearchService;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.BaseAttrInfo;
import com.atguigu.gmall.model.product.BaseCategoryView;
import com.atguigu.gmall.model.product.BaseTrademark;
import com.atguigu.gmall.model.product.SkuInfo;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private ProductFeignClient productFeignClient;

    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RestHighLevelClient restHighLevelClient;


    //上架
    //优化 异步编排多线程
    @Override
    public void upperGoods(Long skuId) {
        //创建对象
        Goods goods = new Goods();
        //赋值
        goods.setId(skuId);
        //远程调用 数据都在
        SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
        goods.setTitle(skuInfo.getSkuName());
        goods.setPrice(skuInfo.getPrice().doubleValue());
        goods.setCreateTime(new Date());
        goods.setDefaultImg(skuInfo.getSkuDefaultImg());

        //获取品牌数据
        BaseTrademark trademark = productFeignClient.getTrademark(skuInfo.getTmId());
        goods.setTmName(trademark.getTmName());
        goods.setTmId(trademark.getId());
        goods.setTmLogoUrl(trademark.getLogoUrl());

        //获取分类数据
        BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
        goods.setCategory1Id(categoryView.getCategory1Id());
        goods.setCategory2Id(categoryView.getCategory2Id());
        goods.setCategory3Id(categoryView.getCategory3Id());
        goods.setCategory1Name(categoryView.getCategory1Name());
        goods.setCategory2Name(categoryView.getCategory2Name());
        goods.setCategory3Name(categoryView.getCategory3Name());

        //平台属性对象集合
        List<BaseAttrInfo> attrList = productFeignClient.getAttrList(skuId);
        ArrayList<SearchAttr> searchAttrArrayList = new ArrayList<>();
        for (BaseAttrInfo baseAttrInfo : attrList) {
            SearchAttr searchAttr = new SearchAttr();
            //平台属性id
            searchAttr.setAttrId(baseAttrInfo.getId());
            //平台属性名
            searchAttr.setAttrName(baseAttrInfo.getAttrName());
            //平台属性值
            searchAttr.setAttrValue(baseAttrInfo.getAttrValueList().get(0).getValueName());
            searchAttrArrayList.add(searchAttr);


        }
        //赋值数据
        goods.setAttrs(searchAttrArrayList);
        //保存
        goodsRepository.save(goods);
    }

    //下架
    @Override
    public void lowerGoods(Long skuId) {
        goodsRepository.deleteById(skuId);
    }

    @Override
    public void updateHostScore(Long skuId) {
        //借助redis
        String hotScoreKey = "hostScore";
        //zincrby
        Double count = redisTemplate.opsForZSet().incrementScore(hotScoreKey, skuId, 1);
        if (count % 10 == 0) {
            //更新es
            Optional<Goods> optional = goodsRepository.findById(skuId);
            Goods goods = optional.get();
            goods.setHotScore(count.longValue());
            goodsRepository.save(goods);
        }

    }

    @Override
    public SearchResponseVo search(SearchParam searchParam) throws IOException {
        /*
        private List<SearchResponseTmVo> trademarkList;
        private List<SearchResponseAttrVo> attrsList = new ArrayList<>();
        private List<Goods> goodsList = new ArrayList<>();
        private Long total;//总记录数
        在一个方法中赋值！
        --------------------------------
        private Integer pageSize;//每页显示的内容
        private Integer pageNo;//当前页面
        private Long totalPages;// 总页数
         */
        //  编写一个方法将dsl 语句 写入searchRequest 对象！
        SearchRequest searchRequest = this.searchRequestDsl(searchParam);

        //  SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

        //  数据集转换  searchResponse---> searchResponseVo  在这个方法中给前四个属性赋值！
        SearchResponseVo searchResponseVo = this.searchResult(searchResponse);
        //  每页显示的大小
        searchResponseVo.setPageSize(searchParam.getPageSize());
        //  默认当前显示第1页
        searchResponseVo.setPageNo(searchParam.getPageNo());
        //  总页数 :  10 3  4    9 3 3
        //  Long totalPages = searchResponseVo.getTotal()%searchParam.getPageSize()==0?searchResponseVo.getTotal()/searchParam.getPageSize():searchResponseVo.getTotal()/searchParam.getPageSize()+1;
        Long totalPages = (searchResponseVo.getTotal() + searchParam.getPageSize() - 1) / searchParam.getPageSize();
        searchResponseVo.setTotalPages(totalPages);
        //  返回数据
        return searchResponseVo;
    }

    /**
     * 返回数据集对象
     *
     * @param searchResponse
     * @return
     */
    private SearchResponseVo searchResult(SearchResponse searchResponse) {
        SearchResponseVo searchResponseVo = new SearchResponseVo();
        /*
        private List<SearchResponseTmVo> trademarkList;
        private List<SearchResponseAttrVo> attrsList = new ArrayList<>();
        private List<Goods> goodsList = new ArrayList<>();
        private Long total;//总记录数
         */
        SearchHits hits = searchResponse.getHits();
        SearchHit[] subHits = hits.getHits();
        //  设置总记录数
        searchResponseVo.setTotal(hits.getTotalHits().value);
        //  声明一个集合来存储goods
        List<Goods> goodsList = new ArrayList<>();
        //  设置检索出来的数据！
        if (subHits != null && subHits.length > 0) {
            //  循环遍历
            for (SearchHit subHit : subHits) {
                //  json 字符串
                String sourceAsString = subHit.getSourceAsString();
                //  转换为Java 对象！ Goods
                Goods goods = JSON.parseObject(sourceAsString, Goods.class);
                //  需要判断是否有高亮字段，如果有高亮字段，则获取高亮的title！
                if (subHit.getHighlightFields().get("title") != null) {
                    Text title = subHit.getHighlightFields().get("title").getFragments()[0];
                    goods.setTitle(title.toString());
                }
                //  添加到集合
                goodsList.add(goods);
            }
        }
        //  赋值数据
        searchResponseVo.setGoodsList(goodsList);

        //  赋值品牌数据
        //  从哪里获取到品牌数据！ 聚合中获取！
        Map<String, Aggregation> aggregationMap = searchResponse.getAggregations().asMap();

        //  获取品牌数据：
        ParsedLongTerms tmIdAgg = (ParsedLongTerms) aggregationMap.get("tmIdAgg");
        //  使用拉姆达表达式做映射！
        List<SearchResponseTmVo> trademarkList = tmIdAgg.getBuckets().stream().map((bucket) -> {
            String keyAsString = ((Terms.Bucket) bucket).getKeyAsString();
            SearchResponseTmVo searchResponseTmVo = new SearchResponseTmVo();
            //  设置品牌Id
            searchResponseTmVo.setTmId(Long.parseLong(keyAsString));
            //  获取品牌名称
            ParsedStringTerms tmNameAgg = ((Terms.Bucket) bucket).getAggregations().get("tmNameAgg");
            String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
            //  设置品牌名称
            searchResponseTmVo.setTmName(tmName);

            //  获取品牌LOGO
            ParsedStringTerms tmLogoUrlAgg = ((Terms.Bucket) bucket).getAggregations().get("tmLogoUrlAgg");
            String tmLogoUrl = tmLogoUrlAgg.getBuckets().get(0).getKeyAsString();
            //  设置品牌名称
            searchResponseTmVo.setTmLogoUrl(tmLogoUrl);
            //  list.add(searchResponseTmVo);
            return searchResponseTmVo;
        }).collect(Collectors.toList());
        searchResponseVo.setTrademarkList(trademarkList);

        //  获取平台属性： 数据类型nested！
        ParsedNested attrAgg = (ParsedNested) aggregationMap.get("attrAgg");
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
        //  获取到 key 给平台属性对象赋值！
        List<SearchResponseAttrVo> attrList = attrIdAgg.getBuckets().stream().map((bucket) -> {
            //  创建一个对象
            SearchResponseAttrVo searchResponseAttrVo = new SearchResponseAttrVo();
            //  获取到平台属性Id
            Number keyAsNumber = ((Terms.Bucket) bucket).getKeyAsNumber();
            searchResponseAttrVo.setAttrId(keyAsNumber.longValue());
            //  获取平台属性名：
            ParsedStringTerms attrNameAgg = ((Terms.Bucket) bucket).getAggregations().get("attrNameAgg");
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
            searchResponseAttrVo.setAttrName(attrName);

            //  获取属性值名称：
            ParsedStringTerms attrValueAgg = ((Terms.Bucket) bucket).getAggregations().get("attrValueAgg");
            List<String> valueNameList = attrValueAgg.getBuckets().stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());

            //            List<String> valueNameList = attrValueAgg.getBuckets().stream().map(buckets -> {
            //                return ((Terms.Bucket) buckets).getKeyAsString();
            //            }).collect(Collectors.toList());
            //  遍历来获取平台属性值名称：
            //            List<String> valueNameList = new ArrayList<>();
            //            for (Terms.Bucket attrValueAggBucket : attrValueAgg.getBuckets()) {
            //                String valueName = attrValueAggBucket.getKeyAsString();
            //                valueNameList.add(valueName);
            //            }

            searchResponseAttrVo.setAttrValueList(valueNameList);
            //  返回平台属性对象数据
            return searchResponseAttrVo;
        }).collect(Collectors.toList());
        //  赋值平台属性对象集合
        searchResponseVo.setAttrsList(attrList);

        return searchResponseVo;
    }

    /**
     * 创建SearchRequest 对象
     *
     * @param searchParam
     * @return
     */
    private SearchRequest searchRequestDsl(SearchParam searchParam) {
        //  构建查询器： {}
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //  {bool}
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        //  判断条件是否存在！
        if (!StringUtils.isEmpty(searchParam.getKeyword())) {
            //  说是通过关键词检索！ {query -- bool --- must --- match title value}
            boolQueryBuilder.must(QueryBuilders.matchQuery("title", searchParam.getKeyword()).operator(Operator.AND));
            //  高亮：
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("title");
            highlightBuilder.preTags("<span style=color:red>");
            highlightBuilder.postTags("</span>");
            searchSourceBuilder.highlighter(highlightBuilder);
        }

        //  用户可能根据分类Id 检索！

        if (!StringUtils.isEmpty(searchParam.getCategory3Id())) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id", searchParam.getCategory3Id()));
        }
        if (!StringUtils.isEmpty(searchParam.getCategory2Id())) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id", searchParam.getCategory2Id()));
        }
        if (!StringUtils.isEmpty(searchParam.getCategory1Id())) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id", searchParam.getCategory1Id()));
        }

        //  品牌Id    trademark=1:小米
        String trademark = searchParam.getTrademark();
        if (!StringUtils.isEmpty(trademark)) {
            //  使用分隔符分割数据
            String[] split = trademark.split(":");
            if (split != null && split.length == 2) {
                //  获取到品牌Id
                boolQueryBuilder.filter(QueryBuilders.termQuery("tmId", split[0]));
            }
        }

        //  按照平台属性值进行过滤！
        //  &props=24:256G:机身内存&props=23:4G:运行内存
        String[] props = searchParam.getProps();
        if (props != null && props.length > 0) {
            //  遍历： 24:256G:机身内存
            for (String prop : props) {
                //  分割字符串
                String[] split = prop.split(":");
                if (split != null && split.length == 3) {
                    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
                    //  最内存的bool
                    BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery();
                    subBoolQuery.must(QueryBuilders.termQuery("attrs.attrId", split[0]));
                    subBoolQuery.must(QueryBuilders.termQuery("attrs.attrValue", split[1]));
                    //  中间的bool
                    boolQuery.must(QueryBuilders.nestedQuery("attrs", subBoolQuery, ScoreMode.None));
                    //  将中间的bool 放入最外层！
                    boolQueryBuilder.filter(boolQuery);
                }
            }
        }

        //  {query -- bool}
        searchSourceBuilder.query(boolQueryBuilder);
        //  分页：
        int from = (searchParam.getPageNo() - 1) * searchParam.getPageSize(); // 从第几条开始查询！
        searchSourceBuilder.from(from);

        //  设置默认显示3条数据！
        searchSourceBuilder.size(searchParam.getPageSize());

        //  排序：order=1:desc order=1:asc  order=2:desc order=2:asc   1:表示综合排序 按照hotScore 2:表示价格 按照price
        String order = searchParam.getOrder();
        if (!StringUtils.isEmpty(order)) {
            //  声明一个字段
            String field = "";
            //  分割字符串
            String[] split = order.split(":");
            if (split != null && split.length == 2) {
                switch (split[0]) {
                    case "1":
                        field = "hotScore";
                        break;
                    case "2":
                        field = "price";
                        break;
                }
                searchSourceBuilder.sort(field, "asc".equals(split[1]) ? SortOrder.ASC : SortOrder.DESC);
            } else {
                searchSourceBuilder.sort("hotScore", SortOrder.DESC);
            }
        }
        //  聚合：
        //  品牌聚合：
        searchSourceBuilder.aggregation(AggregationBuilders.terms("tmIdAgg").field("tmId")
                .subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName"))
                .subAggregation(AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogoUrl")));

        //  平台属性聚合：
        searchSourceBuilder.aggregation(AggregationBuilders.nested("attrAgg", "attrs")
                .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId")
                        .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"))
                        .subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"))
                )
        );

        //  GET /goods/_search
        SearchRequest searchRequest = new SearchRequest("goods");
        searchRequest.source(searchSourceBuilder);
        //  id,price,title,defaultImage,
        searchSourceBuilder.fetchSource(new String[]{"id", "defaultImg", "title", "price"}, null);

        System.out.println("dsl:\t" + searchSourceBuilder.toString());
        //  返回数据！
        return searchRequest;
    }
}
